
#include "UTL2Cache.hpp"
#include "openvip_third/xs_coupledl2.h"
#include "openvip/mem.h"
#include <map>

//  make RUN=tb_coupledl2_cache_coroutine SIMULATOR=vcs WAVE_FORMAT=FSDB
//  TRACE=ON

using namespace ovip;

#if ENABLE_XCOROUTINE
xspcomm::xcorutine<> dcache_wr(XSCoupledL2UnitCor &c, SimpleMemory &gm)
{
    while (true) {
        bool success   = false;
        u_int64_t addr = xRandom(0, 2 * (1L << 20));
        u_int8_t data  = xRandom(0, 255);
        if (xRandom(0, 2) == 0) {
            // write
            auto rw = co_await c.CpuStore(addr, data);
            if (rw) { gm.Write(addr, 1, data); }
        } else {
            // read
            auto r_data = co_await c.CpuLoad(addr, &success);
            if (success) {
                auto m_data = (u_int8_t)gm.Read(addr, 1);
                Assert(
                    r_data == m_data,
                    "Load data(addr: 0x%lx) compare failed! 0x%02x != 0x%02x",
                    addr, r_data, m_data);
            }
        }
    }
};

xspcomm::xcorutine<> icahe_r(XSCoupledL2UnitCor &c, SimpleMemory &gm)
{
    while (true) {
        bool success   = false;
        u_int64_t addr = xRandom(0, 2 * (1L << 20));
        if (xRandom(0, 2) == 0) {
            auto r_data = co_await c.CpuGet(addr, &success);
            if (success) {
                auto m_data = (u_int8_t)gm.Read(addr, 1);
                Assert(r_data == m_data,
                       "Get data(addr: 0x%lx) compare failed! 0x%02x != 0x%02x",
                       addr, r_data, m_data);
            }
        } else {
            co_await XNext();
        }
    }
}

void test_xs_coupuledl2_fullsys(int c, char **v)
{
    UTL2Cache dut({"dut/UT_coupledl2_cache_trace/libDPIL2Cache.so",
                   "+vcs+initreg+random"});
    std::function<int(bool)> stepfunc = [&dut](bool d) {
        dut.step(d);
        return 0;
    };

    XClock clk(stepfunc, {&dut.clock}, {&dut.port});
    dut.reset = 1;
    for (int i = 0; i < 1000; i++) { clk.Step(); }
    dut.reset = 0;
    clk.Step(2000);
    clk.StepRis([](u_int64_t c, void *) { schedule_awit(); });

    Debug("reset cache complete!");

    bool print_dcache_trace = false;

    XSCoupledL2UnitCor core_0_dcache(1, "core0_d", dut.port, clk,
                                         "master_port_0_0_", true,
                                         print_dcache_trace);
    XSCoupledL2UnitCor core_0_icache(2, "core0_i", dut.port, clk,
                                         "master_port_1_0_", false);
    XSCoupledL2UnitCor core_1_dcache(3, "core1_d", dut.port, clk,
                                         "master_port_2_0_", true,
                                         print_dcache_trace);
    XSCoupledL2UnitCor core_1_icache(4, "core1_i", dut.port, clk,
                                         "master_port_3_0_", false);
    SimpleMemory goldn("golden", 4, 1024, 0xff);

    dcache_wr(core_0_dcache, goldn);
    dcache_wr(core_1_dcache, goldn);
    icahe_r(core_0_icache, goldn);
    icahe_r(core_1_icache, goldn);

    u_int64_t s_time = uTime();
    u_int64_t s_clk  = clk.clk;
    while (clk.clk < 10000000) {
        clk.Step();
        if (clk.clk % 10001 == 0) {
            Debug("[%ld] speed: %.2f c/s", clk.clk,
                  1000000 * (clk.clk - s_clk) / float(uTime() - s_time));
            s_time = uTime();
            s_clk  = clk.clk;
        }
    }
    Debug("Test complete!");
}
#endif

int main(int c, char **v)
{
    Debug("test: test_xs_coupuledl2_fullsys");
#if !ENABLE_XCOROUTINE
    Debug("C++20 coroutine is not supported!");
#else
    test_xs_coupuledl2_fullsys(c, v);
#endif
}
